<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="./static/image/favicon.ico">
    <link rel="stylesheet" type="text/css" href="static/element-ui/element.min.css">
    <link rel="stylesheet" type="text/css" href="./static/nprogress/nprogress.css">
    <script type="text/javascript" src="./static/vue/vue.min.js"></script>
    <script type="text/javascript" src="./static/element-ui/element.min.js"></script>
    <script type="text/javascript" src="./static/nprogress/nprogress.js"></script>
    <script type="text/javascript" src="./static/axios/axios.min.js"></script>
    <title>Bolt Manager</title>
    <style>
        [v-cloak] {
            display: none !important;
        }

        body {
            margin: 0;
        }

        #app {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            display: flex;
        }

        .mask-div {
            width: 100%;
            height: 100%;
            position: absolute;
            z-index: 10000;
        }

        .div-left {
            min-width: 250px;
            height: 100%;
            background-color: #001629;
            color: #bbb;
            font-size: 14px;
            display: flex;
            flex-direction: column;
            user-select: none;
        }

        .div-left-title {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 30px;
            border-bottom: 1px #888 solid;
        }

        .div-left-content {
            flex: 1;
            overflow-y: auto;
        }

        .div-left-content-item {
            height: 40px;
            display: flex;
            align-items: center;
            padding-left: 15px;
            cursor: pointer;
        }

        .div-left-content-item:hover {
            color: #fff;
        }

        .div-left-content-item-active {
            color: #fff !important;
            background-color: #409EFF !important;
        }

        .div-left-content-item-icon {
            margin-right: 5px;
        }

        .div-left-content-item-text {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .div-middle {
            flex: 1;
            min-width: 200px;
            max-width: 350px;
            height: 100%;
            display: flex;
            flex-direction: column;
        }

        .div-middle-content {
            flex: 1;
            overflow: auto;
        }

        .custom-tree-node {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: space-between;
            font-size: 14px;
            padding-right: 8px;
        }

        .div-right {
            flex: 1;
            min-width: 200px;
            max-width: 400px;
            height: 100%;
            border-left: 1px #eee solid;
            border-right: 1px #eee solid;
            font-size: 14px;
            display: flex;
            flex-direction: column;
        }

        .div-right-content {
            flex: 1;
            overflow-y: auto;
        }

        .div-right-content-item {
            min-height: 30px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            padding-left: 8px;
            cursor: pointer;
        }

        .div-right-content-item:hover {
            color: #000;
            background-color: #eee;
        }

        .div-right-content-item-active {
            color: #000 !important;
            background-color: #EEF7FF !important;
        }

        .div-right-page {
            border-top: 1px #DCDFE6 solid;
            overflow-x: auto;
        }

        .code-div {
            flex: 2;
            min-width: 300px;
            height: 100%;
            display: flex;
            flex-direction: column;
        }

        .code-div-radio {
            padding-left: 10px;
            border-bottom: 1px #DCDFE6 solid;
            height: 31px;
            display: flex;
            align-items: center;
        }

        .code-div-radio .el-radio {
            margin-right: 10px;
        }

        .code-div-content {
            flex: 1;
            overflow-y: auto;
            padding: 0 10px;
        }

        .code-div-content-pre {
            white-space: pre-wrap;
            word-wrap: break-word;
        }

        .none-data {
            min-height: 60px;
            width: 100%;
            color: #909399;
            font-size: 14px;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .top-button {
            width: 100%;
            border-left: none;
            border-right: none;
            border-radius: 0;
        }

        .right-button {
            margin-left: 10px;
            min-width: 40px;
        }

        .right-button .el-button + .el-button {
            margin-left: 0;
        }

        .el-dialog, .el-message-box {
            background: #fcfcfc !important;
            min-width: 360px;
            max-width: 90%;
        }

        .el-dialog .el-dialog__body {
            border-top: 1px #dedede solid;
            border-bottom: 1px #dedede solid;
        }

        .el-tree > .el-tree-node {
            display: inline-block;
            min-width: 100%;
        }

        .json-string {
            color: green;
        }

        .json-number {
            color: darkorange;
        }

        .json-boolean {
            color: blue;
        }

        .json-null {
            color: magenta;
        }

        .json-key {
            color: red;
        }
    </style>
</head>
<body>
<div id="app" v-cloak>
    <div class="mask-div" v-show="loading"></div>
    <div class="div-left">
        <div class="div-left-title">选择一个数据库</div>
        <div class="div-left-content">
            <div class="div-left-content-item" :class="currentDataBase === item ? 'div-left-content-item-active' : ''"
                 v-for="item in dataBaseList" :key="item"
                 @click="queryBucketTree(item)">
                <span class="div-left-content-item-icon"><i class="el-icon-coin"></i></span>
                <span class="div-left-content-item-text">{{item}}</span>
            </div>
        </div>
    </div>
    <div class="div-middle">
        <el-button class="top-button" size="small" @click="addBucketClick()">添加顶层存储桶</el-button>
        <div class="div-middle-content">
            <el-tree :data="bucketTree" :props="bucketTreeProps" default-expand-all highlight-current
                     :expand-on-click-node="false" @node-click="bucketTreeClick">
            <span class="custom-tree-node" slot-scope="{ node, data }">
                <span>{{ data.name }}</span>
                <span class="right-button">
                  <el-button type="text" size="mini" icon="el-icon-plus" @click.stop="addBucketClick(node)"></el-button>
                  <el-button type="text" size="mini" icon="el-icon-delete"
                             @click.stop="deleteBucketClick(node)"></el-button>
                </span>
              </span>
            </el-tree>
        </div>
    </div>
    <div class="div-right">
        <el-button class="top-button" size="small" @click="addBucketKeyClick">添加键值对</el-button>
        <div v-show="bucketKeys.length === 0" class="none-data">暂无数据</div>
        <div class="div-right-content">
            <div class="div-right-content-item"
                 :class="currentBucketKey === item ? 'div-right-content-item-active' : ''"
                 v-for="item in bucketKeys" :key="item" @click="bucketKeyClick(item)">
                <span>{{item}}</span>
                <span class="right-button">
                    <el-button type="text" size="mini" icon="el-icon-edit"
                               @click.stop="updateBucketKeyClick(item)"></el-button>
                    <el-button type="text" size="mini" icon="el-icon-delete"
                               @click.stop="deleteBucketKeyClick(item)"></el-button>
                </span>
            </div>
        </div>
        <el-pagination
                class="div-right-page"
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
                :page-sizes="[100, 200, 500, 1000, 2000, 5000]"
                :current-page="page.current"
                :page-size="page.size"
                :total="page.total"
                layout="total, sizes, prev, pager, next, jumper"
                align="center"
        >
        </el-pagination>
    </div>
    <div class="code-div">
        <div class="code-div-radio">
            <el-radio size="small" v-model="showValueType" label="json">json</el-radio>
            <el-radio size="small" v-model="showValueType" label="text">text</el-radio>
        </div>
        <div v-show="!showValue" class="none-data">暂无数据</div>
        <div class="code-div-content">
            <pre class="code-div-content-pre" v-html="showValue"></pre>
        </div>
    </div>
    <el-dialog
            :title="dialog.title"
            :visible.sync="dialog.visible"
            :close-on-click-modal="false"
            :close-on-press-escape="false"
            :show-close="false"
            width="600px">
        <el-form :model="condition" :rules="rules" ref="form" label-width="70px" @submit.native.prevent>
            <el-form-item label="key" prop="key">
                <el-input placeholder="请输入key" v-model="condition.key" :disabled="dialog.keyDisabled"></el-input>
            </el-form-item>
            <el-form-item label="value" prop="value" v-if="dialog.hasValue">
                <el-input type="textarea" :rows="5" placeholder="请输入value" v-model="condition.value"></el-input>
            </el-form-item>
        </el-form>
        <span slot="footer">
            <el-button @click="dialogClose">取消</el-button>
            <el-button type="primary" @click="dialogSave" :loading="loading">保存</el-button>
        </span>
    </el-dialog>
</div>
<script type="text/javascript">
    const app = new Vue({
        el: '#app',
        data() {
            return {
                loading: false,
                dataBaseList: [],
                currentDataBase: '',
                bucketTree: [],
                bucketTreeProps: {
                    label: 'name'
                },
                currentBucketLink: [],
                bucketKeys: [],
                currentBucketKey: '',
                currentBucketKeyValue: '',
                showValue: '',
                showValueType: 'json',
                updateBucketLink: [],
                condition: {
                    key: '',
                    value: ''
                },
                rules: {
                    key: [
                        {required: true, message: '请输入key', trigger: 'blur'}
                    ]
                },
                dialog: {
                    visible: false,
                    title: '',
                    keyDisabled: false,
                    hasValue: false,
                    type: ''
                },
                page: {
                    current: 1,
                    size: 500,
                    total: 0
                }
            }
        },
        watch: {
            loading(val) {
                if (val) {
                    NProgress.start()
                } else {
                    NProgress.done()
                }
            },
            currentBucketKeyValue(val) {
                if (this.showValueType === 'json') {
                    try {
                        let json = JSON.stringify(JSON.parse(val), null, 4)
                        this.showValue = this.jsonHighlight(json)
                    } catch (e) {
                        this.showValue = val
                    }
                } else {
                    this.showValue = val
                }
            },
            showValueType(value) {
                if (value === 'json') {
                    try {
                        let json = JSON.stringify(JSON.parse(this.currentBucketKeyValue), null, 4)
                        this.showValue = this.jsonHighlight(json)
                    } catch (e) {
                        this.showValue = this.currentBucketKeyValue
                    }
                } else {
                    this.showValue = this.currentBucketKeyValue
                }
            }
        },
        mounted() {
            this.queryDataBaseList()
        },
        methods: {
            /**
             * 查询数据库列表
             */
            queryDataBaseList() {
                this.loading = true
                axios.get('./api/queryDataBaseList').then(res => {
                    this.loading = false
                    if (res.data.code === 200) {
                        this.dataBaseList = res.data.data
                    } else {
                        this.$message({
                            type: 'error',
                            message: res.data.message
                        })
                    }
                }).catch(() => {
                    this.loading = false
                    this.$message({
                        type: 'error',
                        message: '查询失败'
                    })
                })
            },
            /**
             * 查询存储桶tree
             */
            queryBucketTree(dbName) {
                this.currentBucketLink = []
                this.bucketKeys = []
                this.currentBucketKey = ""
                this.currentBucketKeyValue = ""
                this.currentDataBase = dbName
                this.loading = true
                this.page.current = 1
                this.page.total = 0
                axios.post('./api/queryBucketTree', {dbName: this.currentDataBase}).then(res => {
                    this.loading = false
                    if (res.data.code === 200) {
                        this.bucketTree = res.data.data
                    } else {
                        this.$message({
                            type: 'error',
                            message: res.data.message
                        })
                    }
                }).catch(() => {
                    this.loading = false
                    this.$message({
                        type: 'error',
                        message: '查询失败'
                    })
                })
            },
            /**
             * 点击存储桶
             */
            bucketTreeClick(data, node, currentBucketLink) {
                this.bucketKeys = []
                this.currentBucketKey = ""
                this.currentBucketKeyValue = ""
                if (node) {
                    this.page.current = 1
                    this.currentBucketLink = this.getBucketLink(node)
                } else {
                    this.currentBucketLink = currentBucketLink
                }
                this.loading = true
                axios.post('./api/queryBucketKeys', {
                    dbName: this.currentDataBase,
                    bucketLink: this.currentBucketLink,
                    current: this.page.current,
                    size: this.page.size
                }).then(res => {
                    this.loading = false
                    if (res.data.code === 200) {
                        this.bucketKeys = res.data.data.rows
                        this.page.total = res.data.data.total
                    } else {
                        this.$message({
                            type: 'error',
                            message: res.data.message
                        })
                    }
                }).catch(() => {
                    this.loading = false
                    this.$message({
                        type: 'error',
                        message: '查询失败'
                    })
                })
            },
            /**
             * 查询键值对
             */
            bucketKeyClick(key) {
                return new Promise((resolve, reject) => {
                    this.currentBucketKeyValue = ""
                    this.currentBucketKey = key
                    this.loading = true
                    axios.post('./api/queryKeyValue', {
                        dbName: this.currentDataBase,
                        bucketLink: this.currentBucketLink,
                        key: this.currentBucketKey
                    }).then(res => {
                        this.loading = false
                        if (res.data.code === 200) {
                            this.currentBucketKeyValue = res.data.data
                            resolve(res.data.data)
                        } else {
                            this.$message({
                                type: 'error',
                                message: res.data.message
                            })
                            reject()
                        }
                    }).catch(() => {
                        this.loading = false
                        this.$message({
                            type: 'error',
                            message: '查询失败'
                        })
                        reject()
                    })
                })
            },
            /**
             * 添加存储桶
             */
            addBucketClick(node) {
                if (!this.currentDataBase) {
                    return
                }
                this.dialog.title = '添加存储桶'
                this.dialog.type = 'addBucket'
                this.dialog.keyDisabled = false
                this.dialog.hasValue = false
                this.dialog.visible = true
                if (node) {
                    this.updateBucketLink = this.getBucketLink(node)
                } else {
                    this.updateBucketLink = []
                }
            },
            /**
             * 删除存储桶
             */
            deleteBucketClick(node) {
                this.$confirm('是否删除存储桶：' + node.label, '确认信息', {
                    distinguishCancelAndClose: true,
                    confirmButtonText: '确定',
                    cancelButtonText: '取消'
                }).then(() => {
                    this.loading = true
                    axios.post('./api/deleteBucket', {
                        dbName: this.currentDataBase,
                        bucketLink: this.getBucketLink(node)
                    }).then(res => {
                        if (res.data.code === 200) {
                            // 重新加载存储桶tree
                            this.queryBucketTree(this.currentDataBase)
                        } else {
                            this.loading = false
                            this.$message({
                                type: 'error',
                                message: res.data.message
                            })
                        }
                    }).catch(() => {
                        this.loading = false
                        this.$message({
                            type: 'error',
                            message: '删除失败'
                        })
                    })
                }).catch(() => {
                })
            },
            /**
             * 添加键值对
             */
            addBucketKeyClick() {
                if (this.currentBucketLink.length === 0) {
                    return
                }
                this.dialog.title = '添加键值对'
                this.dialog.type = 'addBucketKey'
                this.dialog.keyDisabled = false
                this.dialog.hasValue = true
                this.dialog.visible = true
            },
            /**
             * 更新键值对
             */
            updateBucketKeyClick(key) {
                if (this.currentBucketLink.length === 0) {
                    return
                }
                this.bucketKeyClick(key).then(res => {
                    this.condition.key = key
                    this.condition.value = res
                    this.dialog.title = '更新键值对'
                    this.dialog.type = 'updateBucketKey'
                    this.dialog.keyDisabled = true
                    this.dialog.hasValue = true
                    this.dialog.visible = true
                })
            },
            /**
             * 删除键值对
             */
            deleteBucketKeyClick(key) {
                this.$confirm('是否删除键值对：' + key, '确认信息', {
                    distinguishCancelAndClose: true,
                    confirmButtonText: '确定',
                    cancelButtonText: '取消'
                }).then(() => {
                    this.loading = true
                    axios.post('./api/deleteBucketKey', {
                        dbName: this.currentDataBase,
                        bucketLink: this.currentBucketLink,
                        key: key
                    }).then(res => {
                        if (res.data.code === 200) {
                            this.page.current = 1
                            // 重新加载键值对
                            this.bucketTreeClick(null, null, this.currentBucketLink)
                        } else {
                            this.loading = false
                            this.$message({
                                type: 'error',
                                message: res.data.message
                            })
                        }
                    }).catch(() => {
                        this.loading = false
                        this.$message({
                            type: 'error',
                            message: '删除失败'
                        })
                    })
                }).catch(() => {
                })
            },
            /**
             * 保存弹窗
             */
            dialogSave() {
                this.$refs.form.validate(valid => {
                    if (valid) {
                        switch (this.dialog.type) {
                            case 'addBucket': // 添加存储桶
                                this.loading = true
                                axios.post('./api/addBucket', {
                                    dbName: this.currentDataBase,
                                    bucketLink: this.updateBucketLink,
                                    key: this.condition.key
                                }).then(res => {
                                    if (res.data.code === 200) {
                                        // 重新加载存储桶tree
                                        this.queryBucketTree(this.currentDataBase)
                                        this.dialogClose()
                                    } else {
                                        this.loading = false
                                        this.$message({
                                            type: 'error',
                                            message: res.data.message
                                        })
                                    }
                                }).catch(() => {
                                    this.loading = false
                                    this.$message({
                                        type: 'error',
                                        message: '添加失败'
                                    })
                                })
                                break
                            case 'addBucketKey': // 添加键值对
                                this.loading = true
                                axios.post('./api/addBucketKey', {
                                    dbName: this.currentDataBase,
                                    bucketLink: this.currentBucketLink,
                                    key: this.condition.key,
                                    value: this.condition.value
                                }).then(res => {
                                    if (res.data.code === 200) {
                                        // 重新加载键值对
                                        this.bucketTreeClick(null, null, this.currentBucketLink)
                                        this.dialogClose()
                                    } else {
                                        this.loading = false
                                        this.$message({
                                            type: 'error',
                                            message: res.data.message
                                        })
                                    }
                                }).catch(() => {
                                    this.loading = false
                                    this.$message({
                                        type: 'error',
                                        message: '添加失败'
                                    })
                                })
                                break
                            case 'updateBucketKey': // 更新键值对
                                this.loading = true
                                axios.post('./api/updateBucketKey', {
                                    dbName: this.currentDataBase,
                                    bucketLink: this.currentBucketLink,
                                    key: this.condition.key,
                                    value: this.condition.value
                                }).then(res => {
                                    this.loading = false
                                    if (res.data.code === 200) {
                                        // 更新键值对
                                        this.currentBucketKeyValue = this.condition.value
                                        this.dialogClose()
                                    } else {
                                        this.$message({
                                            type: 'error',
                                            message: res.data.message
                                        })
                                    }
                                }).catch(() => {
                                    this.loading = false
                                    this.$message({
                                        type: 'error',
                                        message: '更新失败'
                                    })
                                })
                                break
                        }
                    } else {
                        return false
                    }
                })
            },
            /**
             * 关闭弹窗
             */
            dialogClose() {
                this.dialog.visible = false
                this.$refs.form.resetFields()
                this.condition.key = ''
                this.condition.value = ''
            },
            /**
             * 整理存储桶顺序链
             */
            getBucketLink(node) {
                const bucketLink = [node.label]
                let nodeParent = node.parent
                while (nodeParent && nodeParent.level !== 0) {
                    bucketLink.unshift(nodeParent.label)
                    nodeParent = nodeParent.parent
                }
                return bucketLink
            },
            /**
             * json格式高亮
             * @param json
             * @returns {string}
             */
            jsonHighlight(json) {
                json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
                return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, match => {
                    let cls = 'number'
                    if (/^"/.test(match)) {
                        if (/:$/.test(match)) {
                            cls = 'key'
                        } else {
                            cls = 'string'
                        }
                    } else if (/true|false/.test(match)) {
                        cls = 'boolean'
                    } else if (/null/.test(match)) {
                        cls = 'null'
                    }
                    return '<span class="json-' + cls + '">' + match + '</span>'
                })
            },
            /**
             * 每页显示条数变化
             */
            handleSizeChange(size) {
                this.page.current = 1
                this.page.size = size
                this.bucketTreeClick(null, null, this.currentBucketLink)
            },
            /**
             * 当前页码变化
             */
            handleCurrentChange(current) {
                this.page.current = current
                this.bucketTreeClick(null, null, this.currentBucketLink)
            }
        }
    })
</script>
</body>
</html>